open Types
open Num

(*
Ces deux tableaux vont contenir les variables et les fonctions déclarées
  - var_list est sous la forme : "nom de la variable" : [Lexeme valeure]    
  - fun_list est sous la forme : "nom de la fonction" : (["a"; "b"], Tree)   

*)

let var_list = Hashtbl.create 42
let fun_list = Hashtbl.create 42

(*
  Instructions relatives à la machines : elles font appels à des fonctions ocaml
  A terme, le but est de réduire le nombre de fonctions de la sorte en utilisant une petite librairie standart.
*)

let get_n s n e = 
  try 
    let n = Num.int_of_num n in
      String.make 1 (s.[n])
  with  _ -> Helper.fail ("Invalid argument for get", "Argument invalide pour `get`") e

let machine_inst e = function
  | ("+", [NUM a; NUM b]) -> NUM (a+/b)
  | ("-", [NUM a; NUM b]) -> NUM (a-/b)
  | ("*", [NUM a; NUM b]) -> NUM (a*/b)
  | ("/", [NUM a; NUM b]) -> NUM (a//b)
  | ("%", [NUM a; NUM b]) -> NUM (mod_num a b)
  | ("^", [NUM a; NUM b]) -> NUM (a**/b)
  | ("==", [NUM a; NUM b]) -> BOOL (a=/b)
  | ("==", [a;b]) -> BOOL (a=b)
  | ("<", [a;b]) -> BOOL (a<b)
  | (">", [a;b]) -> BOOL (a>b)
  | ("<=", [a;b]) -> BOOL (a<=b)
  | (">=", [a;b]) -> BOOL (a>=b)

  | ("print", [e]) ->  print_string (Types.print_lex e); UNIT ()
  | ("scan", [UNIT _]) -> STRING (read_line())

  | ("string", [NUM a]) -> STRING (string_of_num a)
  | ("num", [STRING a]) -> NUM (Bignum.string_to_num a)
  | ("++", [STRING a; STRING b]) -> STRING (a^b) 
  | ("get", [STRING s; NUM n]) -> STRING (get_n s n e)
  | (a, l) -> List.iter (fun x -> print_endline (d_print_lex x)) l; Helper.fail (("Unknow "^a^" function."), ("Fonction `"^a^"` inconnnue")) e

(* 
   Renvoit l'élement de la liste des arguments appliqués à la fonction correspondant à la liste des arguements de la fonction recherché
   Ex : f est sous la forme f a b = a + b, donc l = ["a"; "b"]
        On cherche "b" (v = "b")
        La liste des lexemes est [NUM a; NUM b].
   La fonction reverra NUM b.
*)
let search_values v l args e = 
  try List.assoc v (List.combine l args) with _ -> Helper.fail (("Unknow "^v), (v^" est inconnu")) e


(*
  Calcul d'une expression à partir de son arbre
*)
let rec calc_v treel  = 
  calc_expr false [] [] treel

(* 
   Calcul une fonction définie par l'utilisateur avec une liste d'arguements `args` passés à cette fonction.
   args est de type lexeme list
*)
and calc_f f args = 
  let (l, treel) = Hashtbl.find fun_list f in 
    calc_expr true l args treel

(*
  Si is_f est à true, la fonction calcule une fonction entrée par l'utilisateur en fonction des arguments f_args (arguments de la fonction)
  et apply_args (arguements passés à la fonction)
  Sinon, exec calcule une expression en fonction de son arbre.
*)
and exec is_f ff fa = function
  | Leaf (VAR v, err) ->  if is_f then search_values v ff fa err else  Helper.find_h var_list v  (("Unknow "^v), (v^" est inconnu")) err
  | Leaf (e, err) ->  e
  | Node ((FUN "if", err), [a;b;c]) ->
      (match (calc_expr is_f ff fa a) with
	 | BOOL a -> calc_expr is_f ff fa (if a then b else c)
	 | _ -> Helper.fail ("Syntax error with if", "Erreur de syntaxe avec if") err
      )
  | Node((FUN s, err), args) -> 
	 ( 
	   let l = List.map (calc_expr is_f ff fa) args in       
	    try (calc_f s l) with _ ->  machine_inst err (s, l)
	 )
  | Node((_, e), _) -> Helper.fail ("Syntax error", "Erreur de syntaxe") e

and calc_expr is_f ff fa e = 
  List.hd (List.rev (List.map (fun x -> exec is_f ff fa x) e))


(*
  Execute une liste d'instructions.
*)
let rec exec_prog res = function
  | [] -> res
  | (Expr e)::l -> let r = calc_v e in exec_prog  (res^(Types.d_print_lex r)^"\n") l
  | (Def (s, t))::l -> let r = calc_v t in Hashtbl.add var_list s r; exec_prog (res^s^" : "^(Types.d_print_lex r)^"\n") l
  | (DFun (n, d, treel))::l -> Hashtbl.add fun_list n (d, treel); exec_prog (res^(Tcheck.get_type_f n)^"\n") l

(*
rc x un n = 
  if n == 0 then un
  else rc x ((1 / (2*un)) * (x +(- un^2)) + un) (n+(-1))
*)
